#define _GNU_SOURCE
#include "persistence.h"
#include "basics/error_handle.h"
#include "basics/macros.h"
#include "netns.h"
#include <dirent.h>
#include <fcntl.h>
#include <sched.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int copy_img(const char *img_file, const int container_id) {
    const char *persistence_path = get_store_path();
    int containers_dir_fd = open(persistence_path, O_RDONLY | O_DIRECTORY);
    /// using it first time, without the ROOT_DIR dir
    if (containers_dir_fd == -1) {
        if_fail_return(mkdir(persistence_path, S_IRWXU | S_IRWXG | S_IRWXO));
    } else {
        if_fail_return(close(containers_dir_fd));
    }
    char root_dir[PATH_MAX];
    snprintf(root_dir, PATH_MAX, "%s/%d", persistence_path, container_id);
    if_fail_return(mkdir(root_dir, S_IRWXU | S_IRWXG | S_IRWXO));
    /// ↓ libtar is not useful and not handy
    // TAR *img_tar;
    // check(tar_open(&img_tar, img_file, NULL, O_RDONLY,
    //                S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH,
    //                TAR_GNU) != -1);
    // /// ignore if the root_dir is not empty, which will return 1
    // check(tar_extract_all(img_tar, root_dir) != -1);
    // check(tar_close(img_tar) != -1);
    /// ↓ so I tried to use tar command istead.
    char command[2 * PATH_MAX];
    snprintf(command, sizeof(command), "tar xf %s --directory=%s", img_file,
             root_dir);
    if_fail_return(system(command));
    create_container_dns(get_root_path(container_id));
    return 0;
}

int store_metadata(const struct metadata meta_data) {
    // char src_path[PATH_MAX];
    // char dst_path[PATH_MAX];
    // snprintf(dst_path, PATH_MAX, "%s/%d/ns", get_store_path(),
    //          meta_data.conatiner_id);
    // check(mkdir(dst_path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) !=
    // -1); snprintf(src_path, PATH_MAX, "/proc/%d/ns", meta_data.root_pid);
    // check(mount(src_path, dst_path, "proc", MS_BIND, NULL) != -1);
    char path[PATH_MAX];
    snprintf(path, PATH_MAX, "%s/%d/metadata", get_store_path(),
             meta_data.conatiner_id);
    int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC,
                  S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    check(fd != -1);
    char buf[BUFF_MAX];
    const int cnt =
        snprintf(buf, BUFF_MAX, "%d %d %d %d", meta_data.root_pid,
                 meta_data.cpu_share, meta_data.mem_limit, meta_data.cpus);
    check(write(fd, buf, cnt + 1) != -1);
    check(close(fd) != -1);
    return 0;
}

int load_metadata(struct metadata *meta_data) {
    char path[PATH_MAX];
    snprintf(path, PATH_MAX, "%s/%d/metadata", get_store_path(),
             meta_data->conatiner_id);
    int fd = open(path, O_RDONLY);
    check(fd != -1);
    char buf[BUFF_MAX];
    check(read(fd, buf, BUFF_MAX) != -1);
    check(close(fd) != -1);
    sscanf(buf, "%d %d %d %d", &meta_data->root_pid, &meta_data->cpu_share,
           &meta_data->mem_limit, &meta_data->cpus);
    return 0;
}

/// path should be a directory
static int delete_recursive(const char *path) {
    DIR *dir = opendir(path);
    check(dir != NULL);
    struct dirent *ent;
    while ((ent = readdir(dir)) != NULL) {
        if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
            continue;
        } else if (ent->d_type == DT_DIR) {
            check(closedir(dir) != -1);
            char inner_path[PATH_MAX];
            snprintf(inner_path, PATH_MAX, "%s/%s", path, ent->d_name);
            if (delete_recursive(inner_path) != 0) {
                /// no such file or dir is okay
                check(errno == ENOENT);
            }
            dir = opendir(path);
            check(dir != NULL);
        } else {
            char abs_path[PATH_MAX];
            snprintf(abs_path, PATH_MAX, "%s/%s", path, ent->d_name);
            check(remove(abs_path) != -1);
        }
    }
    check(closedir(dir) != -1);
    check(remove(path) != -1);
    return 0;
}

int delete_container(const int container_id) {
    char path[PATH_MAX];
    const char *p_root_path;
    p_root_path = get_root_path(container_id);
    strncpy(path, p_root_path, PATH_MAX);
    free((char *)p_root_path);
    delete_recursive(path);
    snprintf(path, PATH_MAX, "%s/%d/container.env", get_store_path(),
             container_id);
    check(remove(path) == 0);
    return 0;
}

/// on the first call, getcwd() will be called
const char *get_store_path() {
    static char *cur_path = NULL;
    if (cur_path == NULL) {
        cur_path = getcwd(NULL, PATH_MAX);
        check(cur_path != NULL);
        strncat(cur_path, "/" STORE_PREFIX, PATH_MAX);
    }
    return cur_path;
}

/// return a heap memory string, user is responsible to call free()
const char *get_root_path(const int container_id) {
    const char *cur_path = get_store_path();
    char *root_path = malloc(PATH_MAX);
    check(root_path != NULL);
    snprintf(root_path, PATH_MAX, "%s/%d/container_root", cur_path,
             container_id);
    return root_path;
}

/// return a heap memory string, user is responsible to call free()
const char *get_env_path(const int container_id) {
    const char *cur_path = get_store_path();
    char *env_file = malloc(PATH_MAX);
    check(env_file != NULL);
    snprintf(env_file, PATH_MAX, "%s/%d/container.env", cur_path, container_id);
    return env_file;
}
